import 'dart:async';
import 'dart:convert';

import 'package:built_collection/built_collection.dart';
import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';
import 'package:http/http.dart' as http;
import 'package:mobile_app.core/core/domain/clients/api_client.dart';
import 'package:mobile_app.core/core/domain/config/config.dart';
import 'package:mobile_app.core/core/domain/entities/purchased_item/purchased_item.dart';
import 'package:mobile_app.core/core/domain/repositories/purchased_item_api_repository/repository.dart';
import 'package:mobile_app.core/core/infrastructure/dtos/api/api_money_amount_dto.dart';
import 'package:mobile_app.core/core/infrastructure/dtos/api/api_time_dto.dart';
import 'package:mobile_app.core/crashlytics/crashlytics.dart';
import 'package:mobile_app.core/logger/logger.dart';

part 'dtos/app_store/app_store_one_time_purchased_item_dto.dart';

part 'dtos/app_store/app_store_one_time_purchased_item_transaction_dto.dart';

part 'dtos/app_store/app_store_purchased_item_dto.dart';

part 'dtos/app_store/app_store_purchased_item_dto_factory.dart';

part 'dtos/app_store/app_store_purchased_subscription_dto.dart';

part 'dtos/app_store/app_store_purchased_subscription_status_dto.dart';

part 'dtos/app_store/app_store_purchased_subscription_transaction_dto.dart';

part 'dtos/app_store/app_store_transaction_dto.dart';

part 'dtos/app_store/app_store_transaction_offer_type_dto.dart';

part 'dtos/app_store/app_store_transaction_ownership_type_dto.dart';

part 'dtos/app_store/app_store_transaction_revocation_info_dto.dart';

part 'dtos/app_store/app_store_transaction_revocation_reason_dto.dart';

part 'dtos/google_play/google_play_one_time_purchased_item_dto.dart';

part 'dtos/google_play/google_play_one_time_purchased_item_transaction_dto.dart';

part 'dtos/google_play/google_play_purchased_item_dto.dart';

part 'dtos/google_play/google_play_purchased_item_dto_factory.dart';

part 'dtos/google_play/google_play_purchased_item_identifier_dto.dart';

part 'dtos/google_play/google_play_purchased_subscription_cancellation_info_dto.dart';

part 'dtos/google_play/google_play_purchased_subscription_cancellation_info_dto_factory.dart';

part 'dtos/google_play/google_play_purchased_subscription_cancellation_survey_reason_dto.dart';

part 'dtos/google_play/google_play_purchased_subscription_cancellation_survey_result_dto.dart';

part 'dtos/google_play/google_play_purchased_subscription_developer_cancellation_info_dto.dart';

part 'dtos/google_play/google_play_purchased_subscription_dto.dart';

part 'dtos/google_play/google_play_purchased_subscription_new_subscription_cancellation_info_dto.dart';

part 'dtos/google_play/google_play_purchased_subscription_pause_info_dto.dart';

part 'dtos/google_play/google_play_purchased_subscription_price_change_status_dto.dart';

part 'dtos/google_play/google_play_purchased_subscription_status_dto.dart';

part 'dtos/google_play/google_play_purchased_subscription_system_cancellation_info_dto.dart';

part 'dtos/google_play/google_play_purchased_subscription_transaction_dto.dart';

part 'dtos/google_play/google_play_purchased_subscription_transaction_offer_type_dto.dart';

part 'dtos/google_play/google_play_purchased_subscription_user_cancellation_info_dto.dart';

part 'dtos/google_play/google_play_transaction_dto.dart';

part 'dtos/google_play/google_play_transaction_revocation_info_dto.dart';

part 'dtos/google_play/google_play_transaction_revocation_reason_dto.dart';

part 'dtos/google_play/google_play_transaction_revocation_source_dto.dart';

part 'dtos/google_play_new_purchased_item_sync_successful_response_dto.dart';

part 'dtos/purchased_item_obtainment_successful_response_dto.dart';

part 'dtos/purchased_item_restoration_successful_response_dto.dart';

part 'dtos/web/web_one_time_purchased_item_dto.dart';

part 'dtos/web/web_one_time_purchased_item_transaction_dto.dart';

part 'dtos/web/web_one_time_purchased_item_transaction_dto_factory.dart';

part 'dtos/web/web_one_time_purchased_item_transaction_offer_type_dto.dart';

part 'dtos/web/web_purchased_item_dto.dart';

part 'dtos/web/web_purchased_item_dto_factory.dart';

part 'dtos/web/web_purchased_subscription_dto.dart';

part 'dtos/web/web_purchased_subscription_status_dto.dart';

part 'dtos/web/web_purchased_subscription_transaction_dto.dart';

part 'dtos/web/web_purchased_subscription_transaction_dto_factory.dart';

part 'dtos/web/web_purchased_subscription_transaction_offer_type_dto.dart';

part 'dtos/web/web_transaction_dto.dart';

part 'dtos/web/yookassa/yookassa_one_time_purchased_item_transaction_dto.dart';

part 'dtos/web/yookassa/yookassa_purchased_subscription_transaction_dto.dart';

part 'dtos/web/yookassa/yookassa_transaction_dto.dart';

part 'dtos/web/yookassa/yookassa_transaction_revocation_info_dto.dart';

class PurchasedItemApiRepositoryImpl implements PurchasedItemApiRepository {
  const PurchasedItemApiRepositoryImpl({
    required ApiClient apiClient,
  }) : _apiClient = apiClient;

  final ApiClient _apiClient;

  @override
  Future<Either<PurchasedItemObtainmentFailure, PurchasedItemObtainmentResult>>
      obtainPurchasedItems({
    required String deviceId,
    String? appStoreReceipt,
    BuiltList<GooglePlayPurchasedItemIdentifier>? googlePlayPurchasedItemIdentifiers,
  }) async {
    final payload = <String, dynamic>{
      'device_id': deviceId,
    };

    if (appStoreReceipt != null) {
      payload['app_store_receipt'] = appStoreReceipt;
    }

    if (googlePlayPurchasedItemIdentifiers != null) {
      final googlePlayPurchasedItemIdentifierJsons =
          googlePlayPurchasedItemIdentifiers.map((purchasedItemIdentifier) {
        return _GooglePlayPurchasedItemIdentifierDto.fromEntity(purchasedItemIdentifier).toJson();
      }).toList();

      payload['google_play_purchased_item_identifiers'] = googlePlayPurchasedItemIdentifierJsons;
    }

    final requestBody = jsonEncode(payload);

    final url = Uri.parse('${Config().serverApiBaseUrl}/obtain-purchased-items');

    const headers = {
      'Content-Type': 'application/json',
    };

    final http.Response response;

    try {
      response = await _apiClient.post(
        url,
        body: requestBody,
        headers: headers,
      );
    } on AccessCredentialObtainmentException {
      const failure = ServerPurchasedItemObtainmentFailure();
      return left(failure);
    } catch (_) {
      const failure = NetworkPurchasedItemObtainmentFailure();
      return left(failure);
    }

    if (response.statusCode == 401) {
      const failure = UnauthorizedPurchasedItemObtainmentFailure();
      return left(failure);
    }

    if (response.statusCode >= 500) {
      const failure = ServerPurchasedItemObtainmentFailure();
      return left(failure);
    }

    if (response.statusCode != 200) {
      Logger().e('Unexpected status code. ${response.body}', null, StackTrace.current);

      Crashlytics().recordError(
        error: Exception('Unexpected status code. ${response.body}'),
        stackTrace: StackTrace.current,
        fatal: false,
      );

      const failure = ServerPurchasedItemObtainmentFailure();

      return left(failure);
    }

    final responseBody = jsonDecode(response.body) as Map<String, dynamic>;
    final successfulResponseDto =
        _PurchasedItemObtainmentSuccessfulResponseDto.fromJson(responseBody);

    final appStorePurchasedItems =
        successfulResponseDto.appStorePurchasedItemDtos.map((appStorePurchasedItemDto) {
      return appStorePurchasedItemDto.toEntity();
    }).toBuiltList();

    final googlePlayPurchasedItems =
        successfulResponseDto.googlePlayPurchasedItemDtos.map((googlePlayPurchasedItemDto) {
      return googlePlayPurchasedItemDto.toEntity();
    }).toBuiltList();

    final webPurchasedItems = successfulResponseDto.webPurchasedItemDtos.map((webPurchasedItemDto) {
      return webPurchasedItemDto.toEntity();
    }).toBuiltList();

    final result = PurchasedItemObtainmentResult(
      appStorePurchasedItems: appStorePurchasedItems,
      googlePlayPurchasedItems: googlePlayPurchasedItems,
      webPurchasedItems: webPurchasedItems,
    );

    return right(result);
  }

  @override
  Future<Either<PurchasedItemRestorationFailure, PurchasedItemRestorationResult>>
      restorePurchasedItems({
    required String deviceId,
    String? appStoreReceipt,
    BuiltList<GooglePlayPurchasedItemIdentifier>? googlePlayPurchasedItemIdentifiers,
  }) async {
    final payload = <String, dynamic>{
      'device_id': deviceId,
    };

    if (appStoreReceipt != null) {
      payload['app_store_receipt'] = appStoreReceipt;
    }

    if (googlePlayPurchasedItemIdentifiers != null) {
      final googlePlayPurchasedItemIdentifierJsons =
          googlePlayPurchasedItemIdentifiers.map((purchasedItemIdentifier) {
        return _GooglePlayPurchasedItemIdentifierDto.fromEntity(purchasedItemIdentifier).toJson();
      }).toList();

      payload['google_play_purchased_item_identifiers'] = googlePlayPurchasedItemIdentifierJsons;
    }

    final requestBody = jsonEncode(payload);

    final url = Uri.parse('${Config().serverApiBaseUrl}/restore-purchased-items');

    const headers = {
      'Content-Type': 'application/json',
    };

    final http.Response response;

    try {
      response = await _apiClient.post(
        url,
        body: requestBody,
        headers: headers,
      );
    } on AccessCredentialObtainmentException {
      const failure = ServerPurchasedItemRestorationFailure();
      return left(failure);
    } catch (_) {
      const failure = NetworkPurchasedItemRestorationFailure();
      return left(failure);
    }

    if (response.statusCode == 401) {
      const failure = UnauthorizedPurchasedItemRestorationFailure();
      return left(failure);
    }

    if (response.statusCode >= 500) {
      const failure = ServerPurchasedItemRestorationFailure();
      return left(failure);
    }

    if (response.statusCode != 200) {
      Logger().e('Unexpected status code. ${response.body}', null, StackTrace.current);

      Crashlytics().recordError(
        error: Exception('Unexpected status code. ${response.body}'),
        stackTrace: StackTrace.current,
        fatal: false,
      );

      const failure = ServerPurchasedItemRestorationFailure();

      return left(failure);
    }

    final responseBody = jsonDecode(response.body) as Map<String, dynamic>;
    final successfulResponseDto =
        _PurchasedItemRestorationSuccessfulResponseDto.fromJson(responseBody);

    final appStorePurchasedItems =
        successfulResponseDto.appStorePurchasedItemDtos.map((appStorePurchasedItemDto) {
      return appStorePurchasedItemDto.toEntity();
    }).toBuiltList();

    final googlePlayPurchasedItems =
        successfulResponseDto.googlePlayPurchasedItemDtos.map((googlePlayPurchasedItemDto) {
      return googlePlayPurchasedItemDto.toEntity();
    }).toBuiltList();

    final webPurchasedItems = successfulResponseDto.webPurchasedItemDtos.map((webPurchasedItemDto) {
      return webPurchasedItemDto.toEntity();
    }).toBuiltList();

    final result = PurchasedItemRestorationResult(
      appStorePurchasedItems: appStorePurchasedItems,
      googlePlayPurchasedItems: googlePlayPurchasedItems,
      webPurchasedItems: webPurchasedItems,
    );

    return right(result);
  }

  @override
  Future<Either<GooglePlayNewPurchasedItemSyncFailure, GooglePlayNewPurchasedItemSyncResult>>
      syncGooglePlayNewPurchasedItem({
    required GooglePlayPurchasedItemIdentifier newPurchasedItemIdentifier,
    required BuiltList<GooglePlayPurchasedItemIdentifier> previousPurchasedItemIdentifiers,
    required String? deviceId,
  }) async {
    final newPurchasedItemIdentifierDto =
        _GooglePlayPurchasedItemIdentifierDto.fromEntity(newPurchasedItemIdentifier);
    final newPurchasedItemIdentifierJson = newPurchasedItemIdentifierDto.toJson();

    final previousPurchasedItemIdentifierJsons =
        previousPurchasedItemIdentifiers.map((purchasedItemIdentifier) {
      return _GooglePlayPurchasedItemIdentifierDto.fromEntity(purchasedItemIdentifier).toJson();
    }).toList();

    final payload = {
      'new_google_play_purchased_item_identifier': newPurchasedItemIdentifierJson,
      'previous_google_play_purchased_item_identifiers': previousPurchasedItemIdentifierJsons,
      'device_id': deviceId,
    };

    final requestBody = jsonEncode(payload);

    final url = Uri.parse('${Config().serverApiBaseUrl}/sync-google-play-new-purchased-item');

    const headers = {
      'Content-Type': 'application/json',
    };

    final http.Response response;

    try {
      response = await _apiClient.post(
        url,
        body: requestBody,
        headers: headers,
      );
    } on AccessCredentialObtainmentException {
      const failure = ServerGooglePlayNewPurchasedItemSyncFailure();
      return left(failure);
    } catch (_) {
      const failure = NetworkGooglePlayNewPurchasedItemSyncFailure();
      return left(failure);
    }

    if (response.statusCode == 401) {
      const failure = UnauthorizedGooglePlayNewPurchasedItemSyncFailure();
      return left(failure);
    }

    if (response.statusCode >= 500) {
      const failure = ServerGooglePlayNewPurchasedItemSyncFailure();
      return left(failure);
    }

    if (response.statusCode != 200) {
      Logger().e('Unexpected status code. ${response.body}', null, StackTrace.current);

      Crashlytics().recordError(
        error: Exception('Unexpected status code. ${response.body}'),
        stackTrace: StackTrace.current,
        fatal: false,
      );

      const failure = ServerGooglePlayNewPurchasedItemSyncFailure();

      return left(failure);
    }

    final responseBody = jsonDecode(response.body) as Map<String, dynamic>;
    final successfulResponseDto =
        _GooglePlayNewPurchasedItemSyncSuccessfulResponseDto.fromJson(responseBody);

    final appStorePurchasedItems =
        successfulResponseDto.appStorePurchasedItemDtos.map((appStorePurchasedItemDto) {
      return appStorePurchasedItemDto.toEntity();
    }).toBuiltList();

    final googlePlayPurchasedItems =
        successfulResponseDto.googlePlayPurchasedItemDtos.map((googlePlayPurchasedItemDto) {
      return googlePlayPurchasedItemDto.toEntity();
    }).toBuiltList();

    final webPurchasedItems = successfulResponseDto.webPurchasedItemDtos.map((webPurchasedItemDto) {
      return webPurchasedItemDto.toEntity();
    }).toBuiltList();

    final result = GooglePlayNewPurchasedItemSyncResult(
      appStorePurchasedItems: appStorePurchasedItems,
      googlePlayPurchasedItems: googlePlayPurchasedItems,
      webPurchasedItems: webPurchasedItems,
    );

    return right(result);
  }
}
